package ca.concordia.soen6011;

import java.util.Scanner;

import ca.concordia.soen6011.impl.Cos;
import ca.concordia.soen6011.impl.LnX;
import ca.concordia.soen6011.impl.Sin;
import ca.concordia.soen6011.impl.Tan;
import ca.concordia.soen6011.impl.TenPowerX;
import ca.concordia.soen6011.impl.XPowerX;
import ca.concordia.soen6011.interfaces.MathFunction;

/**
 * TRANSIENT FUNCTION CALCULATOR (SOEN 6011 - TEAM A)
 * -----------------------------------------------------
 * 
 * This is a custom built transient function calculator featuring 6 functions as
 * follows:
 * 
 * a) Sin(x) b) Cos(x) c) Tan(x) d) 10 power (x) e) x power(x) f) ln power(x)
 * 
 * @author musab.mirza
 * @since July-August 2014
 * 
 *        *****************************************************************
 *        TRANSIENT FUNCTION CALCULATOR is a team project for the SOEN 6011
 *        course delivered by Dr. Kamthan, Pankaj at Concordia University,
 *        Montreal for the summer 2014 session.
 *        *****************************************************************
 */
public class App {

	// Constants
	private static final double pi_degree = 180;

	public static void main(String args[]) {
		new App();
	}

	private App() {
		showMenu();
	}

	private void showMenu() {
		System.out.println("\n\n==== Functions ====\n");
		System.out.println("a) Sin(x)");
		System.out.println("b) Cos(x)");
		System.out.println("c) Tan(x)");
		System.out.println("d) 10 power (x)");
		System.out.println("e) x power(x)");
		System.out.println("f) ln power(x)");
		System.out
				.print("\nEnter the input parameters:\nFormat:<function_code>, <x_value>, <precision>\nFor example: a, 32.411, 4\n\n: ");

		String userInput = getStringInput();
		try {
			validateUserInput(userInput);
		} catch (Exception e) {
			System.out.println("Error: " + e.getMessage());
			showMenu();
		}

		// Now perform it
		try {
			System.out.println(perform(userInput));
		} catch (Exception e) {
			System.out.println("Exception: " + e.getMessage());

		}
		showMenu();
	}

	/**
	 * Parses the user input
	 * 
	 * @param userInput
	 * @throws Exception
	 */
	private Result perform(String userInput) throws Exception {

		String[] parts = userInput.trim().split(",");

		char func = parts[0].trim().charAt(0);

		double x;
		if (parts[1].trim().equalsIgnoreCase("e")) {
			x = Math.E;
		} else if (parts[1].trim().equalsIgnoreCase("p")) {
			x = getPiValue(func);
		} else {
			x = Double.parseDouble(parts[1].trim());
		}

		int temp = Integer.parseInt(parts[2].trim());
		double precision = Math.pow(10, -temp); // We calculate precision in the
												// beginning

		MathFunction mathFunction = getMathFunction(func);
		System.out.println("\nPerforming " + mathFunction.getName() + "...");

		mathFunction.validate(x, precision);
		return mathFunction.compute(x, precision);

	}

	/**
	 * Determines what will be the value of PI based on the selected function
	 * 
	 * @param func
	 * @return
	 */
	private double getPiValue(char func) {
		switch (func) {
		case 'a':
			return pi_degree;
		case 'b':
			return pi_degree;
		case 'c':
			return pi_degree;
		case 'd':
			return Math.PI;
		case 'e':
			return Math.PI;
		case 'f':
			return Math.PI;
		}
		return 0;
	}

	/**
	 * Based on the function character entered, returns the corresponding match
	 * function
	 * 
	 * @param func
	 * @return
	 */
	private MathFunction getMathFunction(char f) {
		switch (f) {
		case 'a':
			return new Sin();
		case 'b':
			return new Cos();
		case 'c':
			return new Tan();
		case 'd':
			return new TenPowerX();
		case 'e':
			return new XPowerX();
		case 'f':
			return new LnX();
		}
		return null;
	}

	/**
	 * Validates the user input
	 * 
	 * @param userInput
	 * @throws Exception
	 */
	private void validateUserInput(String userInput) throws Exception {

		if (userInput == null || userInput.isEmpty())
			throw new Exception("String cannot be empty");

		if (!userInput
				.matches("^[a-f][ ]*,[ ]*-?((\\d+\\.{0,1}\\d*)|[pe])[ ]*,[ ]*[1-9]$"))
			throw new Exception(
					"Invalid format, the proper format is <function_code[a-f]>, <x_value[number/p/e]>, <precision[1-9]>\nFor example: a, 32.411, 4");
	}

	/**
	 * Gets the String input from the user
	 * 
	 * @return
	 */
	private String getStringInput() {
		Scanner input = new Scanner(System.in);
		return input.nextLine();
	}

}
